<!DOCTYPE html>
<html lang="zh-CN">

<!-- Head tag -->
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
  <meta name="baidu-site-verification" content="undefined" />
  <meta name="google-site-verification" content="undefined" />
  <meta name="msvalidate.01" content="undefined" />
  <meta name="360-site-verification" content="undefined" />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- if page has tags, then add tags to keyword -->
  
  
  <meta name="keywords" content=",Golang,Golang源码,">
  <!-- page.description has higher priority -->
  <meta name="description" content="介绍interface和类型断言">
  <link rel="shortcut icon" href="/blog/img/logo.webp">
  <title>
    
    Go学习 接口和类型断言 | Evrn
    
  </title>

  <link rel="canonical" href="/blog/post/go-learn-interface.html">
  
<link rel="stylesheet" href="/blog/css/reset.css">

  
<link rel="stylesheet" href="/blog/css/layout.css">

  
<link rel="stylesheet" href="/blog/css/markdown.css">

  <!-- Pygments Highlight CSS -->
  
<link rel="stylesheet" href="/blog/css/highlight.css">

  
<link rel="stylesheet" href="/blog/css/geektutu.css">

  <!-- global function -->
  <script>
    window.globalAddScript = function (url, onload, onerror) {
      var s = document.createElement('script');
      s.src = url;
      onload && (s.onload = onload);
      onerror && (s.onerror = onerror);
      document.body.appendChild(s);
    }
    window.globalAddCss = function (url) {
      var s = document.createElement('link');
      s.rel = 'stylesheet';
      s.href = url;
      document.body.appendChild(s);
    }
    window.getPosition = function (ele) {
      var x = 0, y = 0;
      while (ele) {
        x += (ele.offsetLeft - ele.scrollLeft + ele.clientLeft);
        y += (ele.offsetTop - ele.scrollTop + ele.clientTop);
        ele = ele.offsetParent;
      }
      return { x: x, y: y };
    }
    window.getDom = function (str) { return document.querySelector(str) }
  </script>
  <!-- google ad -->
  
<meta name="generator" content="Hexo 4.2.0"></head>

<body>
    <header class="gkt-header col-xs-12 padding-0">
    <div id="gkt-nav" class="gkt-header-container">
        <a href="/blog/" class="gkt-header-title float-left">
            <img class="float-left" src="/blog/img/logo.webp" alt="">
            <span>Evrn</span>
        </a>
        <nav class="gkt-header-nav text-right">
            <ul>
                <li><a class="hidden-xs" href="/blog/">主页</a></li>
                <li><a href="/blog/series/">专题</a></li>
                <li><a href="/blog/archives/">归档</a></li>
                <li><a href="/blog/post/link.html">友链</a></li>
                <li><a href="/blog/post/about.html">留言</a></li>
            </ul>
        </nav>
    </div>
    <div id="gkt-cate-nav" class="gkt-header-container hidden-xs">
        
        <nav class="gkt-header-nav float-left">
            <ul>
                
                
                <li class="gkt-cate-name float-left active">
                    <a class="float-left" href="/blog/post/go-learn-interface.html">Golang学习</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-asm-indroduce.html">Golang汇编</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/tcp-three-way-handshake.html">基础</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/go-how-to-build.html">Golang底层</a>
                    
                </li>
                
                <li class="gkt-cate-name float-left ">
                    <a class="float-left" href="/blog/post/mysql-index-use.html">MySQL</a>
                    
                </li>
                
            </ul>
        </nav>
    </div>
</header>
<div style="height: 44px"></div>
<div class="hidden-xs" style="height: 44px"></div>
<script>
    (function () {
        window.addEventListener('scroll', function () {
            if (window.innerWidth < 768) {
                return;
            }
            var nav = document.querySelector('#gkt-nav');
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            scrollTop > 50 && (nav.classList.add('hide'));
            scrollTop <= 50 && (nav.classList.remove('hide'));
        });
        var cateNavs = document.querySelectorAll('#gkt-cate-nav>nav>ul>li');
        [].slice.call(cateNavs).forEach(function (item) {
            var sub = item.querySelector('.gkt-sub-cate');
            if (!sub) return;
            item.addEventListener('mouseenter', function (e) { sub.style.display = 'block'; }, false);
            item.addEventListener('mouseleave', function (e) { sub.style.display = 'none'; }, false);
        })
    })();
</script>
    <!-- Main Content -->
    <div class="main-container">
        <!-- Main Content -->
<main class="col-xs-12 padding-0 markdown-it">
    <!-- Post Container -->
    
    
    <!-- Post Content -->
<div class="float-left post-container box-shadow">
    <div class="u-arrow-wrapper hidden-xs">
        
        <a class="float-left" href="/blog/post/link.html"><i class="u-arrow-left"></i></a>
        
        
        <a class="float-right" href="/blog/post/go-method-params.html"><i class="u-arrow-right"></i></a>
        
    </div>
    <article class="col-xs-12">
        <h1> Go学习 接口和类型断言 </h1>

        

        
        <div class="hidden-lg hidden-md series_links">
            <p> <strong> Golang学习系列文章链接：</strong></p>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-interface.html">Go学习 接口和类型断言</a>
                    <span class="post-item-date">(Jan 21, 2019)</span>
                </li>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html">Go源码学习 sort包</a>
                    <span class="post-item-date">(Mar 11, 2020)</span>
                </li>
                
            </ul>
        </div>
        
        
        
        <h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>接口类型(interface)是一种抽象的类型。它不会暴露出它所代表的对象的内部结构和这个对象支持的基础操作的集合；它们只会展示出它们自己的方法。也就是说当你有看到一个接口类型的值时，你不用知道它是什么，只要知道可以通过它的方法来做什么。<br>这是对其它类型行为的抽象和概括；因为接口类型不会和特定的实现细节绑定在一起，通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。</p>
<h3 id="独特之处"><a href="#独特之处" class="headerlink" title="独特之处"></a>独特之处</h3><p>很多面向对象的语言都有<strong>相似</strong>的接口概念，但Go语言中接口类型的独特之处在于它是满足<strong>隐式实现</strong>的。也就是说，不同于其他语言需要显式地声明这个具体类型实现了什么接口,如</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">A</span> <span class="keyword">implements</span> <span class="title">XXX</span> </span>&#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在Golang中只要<strong>实现</strong>了接口定义的<strong>所有</strong>方法，就是实现了该interface<br>。</p>
<blockquote>
<p>所谓”实现”要满足两个条件</p>
</blockquote>
<blockquote>
<ul>
<li>两个方法的签名需要完全一致</li>
<li>两个方法的名称要一模一样</li>
</ul>
</blockquote>
<p>这是一种无侵入式的接口实现方式或者叫“Duck typing”，中文常译作“鸭<br>子类型”。这种设计好处是可以让你创建一个新的接口类型满足已经存在的具体类型却不用去改变这些类型的定义；当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。</p>
<h3 id="Golang接口的创建和实现"><a href="#Golang接口的创建和实现" class="headerlink" title="Golang接口的创建和实现"></a>Golang接口的创建和实现</h3><p>接口大量应用于golang的标准库中,我们以最常用的fmt包来举例</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> fmt</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Fprintf</span><span class="params">(w io.Writer, format <span class="keyword">string</span>, a ...<span class="keyword">interface</span>&#123;&#125;)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">	p := newPrinter()</span><br><span class="line">	p.doPrintf(format, a)</span><br><span class="line">	n, err = w.Write(p.buf)</span><br><span class="line">	p.free()</span><br><span class="line">	<span class="keyword">return</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Printf</span><span class="params">(format <span class="keyword">string</span>, args ...<span class="keyword">interface</span>&#123;&#125;)</span> <span class="params">(<span class="keyword">int</span>, error)</span></span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Fprintf(os.Stdout, format, args...)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">Sprintf</span><span class="params">(format <span class="keyword">string</span>, args ...<span class="keyword">interface</span>&#123;&#125;)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> buf bytes.Buffer</span><br><span class="line">    Fprintf(&amp;buf, format, args...)</span><br><span class="line">    <span class="keyword">return</span> buf.String()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>两个方法都调用了<strong>Fprintf</strong>,其中就使用了io.Writer这个接口</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io</span><br><span class="line"><span class="keyword">type</span> Writer <span class="keyword">interface</span> &#123;</span><br><span class="line">    Write(p []<span class="keyword">byte</span>) (n <span class="keyword">int</span>, err error)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Reader <span class="keyword">interface</span> &#123;  </span><br><span class="line">   Read(p []<span class="keyword">byte</span>) (n <span class="keyword">int</span>, err error)  </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> Closer <span class="keyword">interface</span> &#123;  </span><br><span class="line">   Close() error  </span><br><span class="line">&#125;  </span><br><span class="line">   </span><br><span class="line"><span class="keyword">type</span> ReadWriter <span class="keyword">interface</span> &#123;  </span><br><span class="line">   Reader  </span><br><span class="line">   Writer</span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">type</span> ReadCloser <span class="keyword">interface</span> &#123;  </span><br><span class="line">   Reader  </span><br><span class="line">   Closer</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中接口io.Writer只定义了一个Write方法,所以只要实现了</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Write(p []<span class="keyword">byte</span>) (n <span class="keyword">int</span>, err error)</span><br></pre></td></tr></table></figure>
<p>这个方法,就实现了io.writer,<br>而我们看到 bytes.Buffer</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> bytes</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(b *Buffer)</span> <span class="title">Write</span><span class="params">(p []<span class="keyword">byte</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">	b.lastRead = opInvalid</span><br><span class="line">	m, ok := b.tryGrowByReslice(<span class="built_in">len</span>(p))</span><br><span class="line">	<span class="keyword">if</span> !ok &#123;</span><br><span class="line">		m = b.grow(<span class="built_in">len</span>(p))</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> <span class="built_in">copy</span>(b.buf[m:], p), <span class="literal">nil</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>和 os.Stdout</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># os.Stdout 的类型就是os.File</span><br><span class="line"><span class="keyword">package</span> os</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(f *File)</span> <span class="title">Write</span><span class="params">(b []<span class="keyword">byte</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">	n, err = f.pfd.Write(b)</span><br><span class="line">	runtime.KeepAlive(f)</span><br><span class="line">	<span class="keyword">return</span> n, err</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>都实现了Write方法,所以都可以作为参数传入Fprintf中进行处理</p>
<h3 id="自定义类型"><a href="#自定义类型" class="headerlink" title="自定义类型"></a>自定义类型</h3><p>如果我要自己实现Write这个接口也只要实现这个方法</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> ByteCounter <span class="keyword">int</span>  </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *ByteCounter)</span> <span class="title">Write</span><span class="params">(p []<span class="keyword">byte</span>)</span> <span class="params">(<span class="keyword">int</span>, error)</span></span> &#123;</span><br><span class="line">    *c += ByteCounter(<span class="built_in">len</span>(p)) <span class="comment">// 统计字节串长度</span></span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">len</span>(p), <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">   <span class="keyword">var</span> c ByteCounter  </span><br><span class="line">   c.Write([]<span class="keyword">byte</span>(<span class="string">"hello"</span>))  </span><br><span class="line">   fmt.Println(c)     <span class="comment">// "5", = len("hello")  </span></span><br><span class="line">   c = <span class="number">0</span>              <span class="comment">//   </span></span><br><span class="line">   <span class="keyword">var</span> name = <span class="string">"Dolly"</span>  </span><br><span class="line">   fmt.Fprintf(&amp;c, <span class="string">"hello, %s"</span>, name)  </span><br><span class="line">   fmt.Println(c)     <span class="comment">// "12", = len("hello, Dolly")</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p><strong>注意:</strong>因为我们<code>ByteCounter</code>的write方法的接口器(receivers)是指针类型所以传入Fprintf时要用<code>&amp;</code>取地址。相应的,如果接受器是值类型那就直接传对象就可以不需要取地址</p>
<h2 id="接口值"><a href="#接口值" class="headerlink" title="接口值"></a>接口值</h2><p>所谓的接口值本质上并不是一个值,而是一个数据结构,主要由两个部分组成</p>
<ul>
<li>类型</li>
<li>该类型的值</li>
</ul>
<p>类型部分代表实现该接口类型的描述符。值就是具体的值。</p>
<p>它们被称为接口的<strong>动态类型</strong>和<strong>动态值</strong>。</p>
<p>源码定义 runtime2.go</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> iface <span class="keyword">struct</span> &#123;</span><br><span class="line">	tab  *itab  <span class="comment">//指向动态类型的指针</span></span><br><span class="line">	data unsafe.Pointer <span class="comment">//指向动态值的指针</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>data虽然是一个指针，但是在每次赋值的时候，其实都会全量拷贝原来的值，然后把拷贝的首地址存放到data</p>
<p>如,传参</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">printiface</span><span class="params">(a <span class="keyword">interface</span>&#123;&#125;)</span></span> &#123;</span><br><span class="line">	...</span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">a := [<span class="number">20</span>]<span class="keyword">byte</span>&#123;&#125;</span><br><span class="line">printiface(a)</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">MOVQ	&quot;&quot;.a+88(SP), AX &#x2F;&#x2F;开始拷贝数组</span><br><span class="line">MOVQ	AX, &quot;&quot;..autotmp_3+128(SP)</span><br><span class="line">MOVUPS	&quot;&quot;.a+92(SP), X0</span><br><span class="line">MOVUPS	X0, &quot;&quot;..autotmp_3+132(SP)</span><br><span class="line">MOVQ	&quot;&quot;.a+88(SP), AX</span><br><span class="line">MOVQ	AX, &quot;&quot;..autotmp_16+148(SP)</span><br><span class="line">MOVUPS	&quot;&quot;.a+92(SP), X0</span><br><span class="line">MOVUPS	X0, &quot;&quot;..autotmp_16+152(SP)</span><br><span class="line">LEAQ	type.[20]uint8(SB), AX &#x2F;&#x2F;把动态类型存放到参数tab域</span><br><span class="line">MOVQ	AX, (SP)</span><br><span class="line">LEAQ	&quot;&quot;..autotmp_16+148(SP), AX</span><br><span class="line">MOVQ	AX, 8(SP)  &#x2F;&#x2F;把数组的首地址存放到参数data域</span><br><span class="line">CALL	&quot;&quot;.printiface(SB)</span><br></pre></td></tr></table></figure>

<p>如，赋值</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">a := [<span class="number">20</span>]<span class="keyword">byte</span>&#123;&#125;</span><br><span class="line"><span class="keyword">var</span> b <span class="keyword">interface</span>&#123;&#125; = <span class="literal">nil</span></span><br><span class="line">b = a</span><br></pre></td></tr></table></figure>

<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">MOVQ	&quot;&quot;.a+88(SP), AX &#x2F;&#x2F;开始拷贝数组</span><br><span class="line">MOVQ	AX, &quot;&quot;..autotmp_3+128(SP)</span><br><span class="line">MOVUPS	&quot;&quot;.a+92(SP), X0  &#x2F;&#x2F;92(SP)开始16个字节一共4+16&#x3D;20字节</span><br><span class="line">MOVUPS	X0, &quot;&quot;..autotmp_3+132(SP) </span><br><span class="line">MOVQ	&quot;&quot;.a+88(SP), AX</span><br><span class="line">MOVQ	AX, &quot;&quot;..autotmp_7+108(SP)</span><br><span class="line">MOVUPS	&quot;&quot;.a+92(SP), X0</span><br><span class="line">MOVUPS	X0, &quot;&quot;..autotmp_7+112(SP)</span><br><span class="line">LEAQ	type.[20]uint8(SB), AX &#x2F;&#x2F;把动态类型存放到b.tab域</span><br><span class="line">MOVQ	AX, &quot;&quot;.b+224(SP)</span><br><span class="line">LEAQ	&quot;&quot;..autotmp_7+108(SP), AX</span><br><span class="line">MOVQ	AX, &quot;&quot;.b+232(SP)  &#x2F;&#x2F;把数组的首地址存放到b.data域</span><br></pre></td></tr></table></figure>

<p>对于一个接口的零值就是它的类型和值的部分都是nil。<br><img src="http://server-1256789886.cos.ap-guangzhou.myqcloud.com/blog_go/evrn/evrn/evrn/9a6cb33e7a8e4eb694b17202814bde23.png" alt="png"></p>
<p>可以通过使用w==nil或者w!=nil来判读接口值是否为空。</p>
<blockquote>
<p>注意:这里判断的其实是type部分,不严谨。</p>
</blockquote>
<p>我们知道os.Stdout实现了io.Write接口<br>则</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> w io.Write</span><br><span class="line">w = os.Stdout</span><br></pre></td></tr></table></figure>
<p>就是对接口的赋值,<br>这个赋值过程调用了一个具体类型到接口类型的<strong>隐式转换</strong>，这和显式的使用<code>io.Writer(os.Stdout)</code>是等价的。于是这个接口值的动态类型被设为<code>*os.Stdout</code>指针的类型描述符，它的动态值持有os.Stdout的<strong>拷贝</strong>；之前说过os.Stdout实质上就是一个代表处理标准输出的os.File类型变量的<strong>指针</strong>。<br>可以通过使用fmt包的%T来打印接口的动态类型:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> w io.Writer</span><br><span class="line">fmt.Printf(<span class="string">"%T\n"</span>, w) <span class="comment">// ""</span></span><br><span class="line">w = os.Stdout</span><br><span class="line">fmt.Printf(<span class="string">"%T\n"</span>, w) <span class="comment">// 动态类型是"*os.File"</span></span><br><span class="line">w = <span class="built_in">new</span>(bytes.Buffer)</span><br><span class="line">fmt.Printf(<span class="string">"%T\n"</span>, w) <span class="comment">// 动态类型是"*bytes.Buffer"</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>动态值持有的是原始值的一份全量拷贝,而不是一个指针引用,只是这里动态值是一个指针,所以它的拷贝也是一个指针。</p>
<p>因此使用接口类型是赋值会有一定的损耗,不能滥用</p>
</blockquote>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main  </span><br><span class="line">  </span><br><span class="line"><span class="keyword">type</span> Pet <span class="keyword">interface</span> &#123;  </span><br><span class="line">   SetName(<span class="keyword">string</span>)  </span><br><span class="line">&#125;  </span><br><span class="line"><span class="keyword">type</span> Dog <span class="keyword">struct</span> &#123;  </span><br><span class="line">   name <span class="keyword">string</span>  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(d Dog)</span> <span class="title">SetName</span><span class="params">(name <span class="keyword">string</span>)</span></span> &#123;  </span><br><span class="line">   d.name = name  </span><br><span class="line">&#125;  </span><br><span class="line">  </span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">   dog1 := Dog&#123;<span class="string">"little pig"</span>&#125;  </span><br><span class="line">   <span class="keyword">var</span> pet Pet = dog1  </span><br><span class="line">   dog1.name = <span class="string">"monster"</span>  <span class="comment">//pet的name仍然是 little pig</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>当我们调用一个包含<code>*os.File</code>类型指针的接口值的Write方法时,</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">w.Write([]<span class="keyword">byte</span>(<span class="string">"hello"</span>)) <span class="comment">// "hello"</span></span><br></pre></td></tr></table></figure>
<p>实质上就是<code>(*os.File).Write</code>方法被调用。这个调用输出“hello”。</p>
<p><img src="http://server-1256789886.cos.ap-guangzhou.myqcloud.com/blog_go/evrn/evrn/evrn/03536012e560466798688814b46e8b2f.png" alt="png"></p>
<p>通常在编译期，我们不知道接口值的动态类型是什么，所以一个接口上的调用必须使用动态分配。因为不是直接进行调用，所以编译器必须把代码生成在类型描述符的方法Write上，然后间接调用那个地址。这个调用的接收者是一个接口动态值的<strong>拷贝</strong>，os.Stdout。效果和下面这个直接调用一样：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">os.Stdout.Write([]byte(&quot;hello&quot;)) &#x2F;&#x2F; &quot;hello&quot;</span><br></pre></td></tr></table></figure>

<p>一个接口值可以持有任意大的动态值。例如，表示时间实例的time.Time类型，这个类型有几个对外不公开的字段。我们从它上面创建一个接口值,这使得接口类型可以类似于其他语言的泛型来使用</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x <span class="keyword">interface</span>&#123;&#125; = time.Now()</span><br></pre></td></tr></table></figure>

<p><img src="http://server-1256789886.cos.ap-guangzhou.myqcloud.com/blog_go/evrn/evrn/evrn/caddc9e4b5154aebb8762cda1d7b0bf6.png" alt="png"></p>
<h2 id="一个包含nil指针的接口不是nil接口"><a href="#一个包含nil指针的接口不是nil接口" class="headerlink" title="一个包含nil指针的接口不是nil接口"></a>一个包含nil指针的接口不是nil接口</h2><p>一个不包含任何值的nil接口值和一个刚好包含nil指针的接口值是不同的。<br>考虑以下代码</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">f</span><span class="params">(out io.Writer)</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> out != <span class="literal">nil</span> &#123;</span><br><span class="line">        out.Write([]<span class="keyword">byte</span>(<span class="string">"done!\n"</span>))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> buf *bytes.Buffer      </span><br><span class="line">    f(buf) </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>当main函数调用函数f时，它给f函数的out参数赋了一个<code>*bytes.Buffer</code>的空指针，所以out的动态值是nil。然而，它的动态类型是<code>*bytes.Buffer</code>，意思就是out变量是一个包含空指针值的非空接口，所以防御性检查out!=nil的结果依然是true。<br><img src="http://server-1256789886.cos.ap-guangzhou.myqcloud.com/blog_go/evrn/evrn/evrn/1f173d194f0644b6a62d23ffcccc221b.png" alt="png"><br>动态分配机制依然决定<code>(*bytes.Buffer).Write</code>的方法会被调用，但是这次的调用者的值是<code>nil</code>。一个<code>nil</code>的<code>*bytes.Buffer</code>指针有实现这个接口的方法，编译器无法发现问题,但是当运行时它尝试去获取缓冲区时就会发生<code>panic</code>。<br>解决方案就是将main函数中的变量buf的类型改为<code>io.Writer</code>，因此可以避免一开始就将一个不完全的值赋值给这个接口：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">f</span><span class="params">(out io.Writer)</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> out != <span class="literal">nil</span> &#123;</span><br><span class="line">        out.Write([]<span class="keyword">byte</span>(<span class="string">"done!\n"</span>))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">    <span class="keyword">var</span> buf io.Write <span class="comment">//初始化为nil      </span></span><br><span class="line">    f(buf) </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><img src="http://server-1256789886.cos.ap-guangzhou.myqcloud.com/blog_go/evrn/evrn/evrn/9a6cb33e7a8e4eb694b17202814bde23.png" alt="png"></p>
<h3 id="坑实例"><a href="#坑实例" class="headerlink" title="坑实例"></a>坑实例</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="string">"fmt"</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> ErrorImpl <span class="keyword">struct</span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(e *ErrorImpl)</span> <span class="title">Error</span><span class="params">()</span> <span class="title">string</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> <span class="string">""</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ei *ErrorImpl</span><br><span class="line"><span class="keyword">var</span> e error</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ErrorImplFun</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> ei  <span class="comment">//ei是nil但是返回就不是nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;</span><br><span class="line">   f := ErrorImplFun()</span><br><span class="line">   fmt.Println(f == <span class="literal">nil</span>) <span class="comment">//false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们知道error本来是一个接口</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> error <span class="keyword">interface</span> &#123;</span><br><span class="line">	Error() <span class="keyword">string</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><code>ErrorImplFun</code> 返回的时候，实际上将<code>ErrorImpl</code>类型的ei赋值到接口，因此返回值是一个动态类型是*ErrorImpl，动态值是nil的值，对比的时候!=nil</p>
<h3 id="如何解决？"><a href="#如何解决？" class="headerlink" title="如何解决？"></a>如何解决？</h3><h4 id="方法一-返回的结果进行非nil检查，然后再赋值给interface-变量"><a href="#方法一-返回的结果进行非nil检查，然后再赋值给interface-变量" class="headerlink" title="方法一: 返回的结果进行非nil检查，然后再赋值给interface{}变量"></a>方法一: 返回的结果进行非nil检查，然后再赋值给interface{}变量</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ErrorImplFun</span><span class="params">()</span> <span class="title">error</span></span> &#123;</span><br><span class="line">   <span class="keyword">if</span> ei == <span class="literal">nil</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">return</span> ei</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h4 id="方法二-返回具体实现的类型而不是interface"><a href="#方法二-返回具体实现的类型而不是interface" class="headerlink" title="方法二:返回具体实现的类型而不是interface{}"></a>方法二:返回具体实现的类型而不是interface{}</h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">ErrorImplFun</span><span class="params">()</span> *<span class="title">ErrorImpl</span></span> &#123;</span><br><span class="line">   <span class="keyword">return</span> ei</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="第三方包解决"><a href="#第三方包解决" class="headerlink" title="第三方包解决"></a>第三方包解决</h3><h4 id="方法一-利用interface-原理"><a href="#方法一-利用interface-原理" class="headerlink" title="方法一: 利用interface{}原理"></a><strong>方法一: 利用interface{}原理</strong></h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> InterfaceStructure <span class="keyword">struct</span> &#123;</span><br><span class="line">	pt <span class="keyword">uintptr</span> <span class="comment">// 到值类型的指针</span></span><br><span class="line">	pv <span class="keyword">uintptr</span> <span class="comment">// 到值内容的指针</span></span><br><span class="line">&#125;</span><br><span class="line">...</span><br><span class="line">is:=*(*InterfaceStructure)(unsafe.Pointer(&amp;i))</span><br><span class="line"><span class="keyword">if</span> is.pt==<span class="number">0</span> &amp;&amp; is.pv==<span class="number">0</span> &#123;</span><br><span class="line">   <span class="comment">//is nil do something</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>将底层指向值和指向值的类型的指针打印出来如果都是0，表示是nil</p>
<h4 id="方法二-利用断言-断言出来具体类型再判断非空"><a href="#方法二-利用断言-断言出来具体类型再判断非空" class="headerlink" title="方法二:利用断言,断言出来具体类型再判断非空"></a><strong>方法二:利用断言,断言出来具体类型再判断非空</strong></h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">res, ok := f.(*ErrorImpl)</span><br><span class="line">fmt.Printf(<span class="string">"ok:%v,f:%v,res:%v"</span>, </span><br><span class="line">ok, f == <span class="literal">nil</span>, res == <span class="literal">nil</span>)</span><br><span class="line"><span class="comment">//ok:true,f:false,res:true</span></span><br></pre></td></tr></table></figure>

<h4 id="方法三-利用反射"><a href="#方法三-利用反射" class="headerlink" title="方法三:利用反射"></a><strong>方法三:利用反射</strong></h4><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rv := reflect.ValueOf(f)</span><br><span class="line">fmt.Printf(<span class="string">"%v"</span>, rv.IsNil())</span><br></pre></td></tr></table></figure>

<h2 id="类型断言"><a href="#类型断言" class="headerlink" title="类型断言"></a>类型断言</h2><p>类型断言是一个使用在接口值上的操作。语法上它看起来像x.(T)被称为断言类型，这里x表示一个接口的类型和T表示一个类型。一个类型断言检查它操作对象的动态类型是否和断言的类型匹配。</p>
<p>类型断言的本质，跟类型转换类似，都是类型之间进行转换，不同之处在于，类型断言只能作用于接口类型。</p>
<p><strong>语法：</strong></p>
<p>　　&lt;目标类型的值&gt;，&lt;布尔参数&gt; := &lt;表达式&gt;<strong>.</strong>( 目标类型 ) // 安全类型断言</p>
<p>　　&lt;目标类型的值&gt; := &lt;表达式&gt;<strong>.</strong>( 目标类型 )　　//非安全类型断言</p>
<p>类型断言分两种情况</p>
<ul>
<li>第一种，如果断言的类型T是一个具体类型，然后类型断言检查x的动态类型是否和T相同。如果这个检查成功了，类型断言的<strong>结果是x的动态值</strong>，当然它的<strong>类型是T</strong>。换句话说，具体类型的类型断言从它的操作对象中获得具体的值(相当于类型转换)。如果检查失败，接下来这个操作会抛出panic。例如：<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> w io.Writer</span><br><span class="line">w = os.Stdout</span><br><span class="line">f := w.(*os.File)      <span class="comment">// success: f == os.Stdout</span></span><br><span class="line">c := w.(*bytes.Buffer) <span class="comment">// panic: interface holds *os.File, not *bytes.Buffer</span></span><br></pre></td></tr></table></figure>


</li>
</ul>
<ul>
<li>第二种，如果相反断言的类型T是一个接口类型，然后类型断言检查是否x的动态类型满足T。如果这个检查成功了，这个结果就是一个动态类型为T,动态值部分为该值本身的接口值。换句话说，对一个接口类型的类型断言改变了类型的表述方式，改变了可以获取的方法集合（通常更大），但是它保护了接口值内部的动态类型和值的部分。<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> w io.Writer</span><br><span class="line">w = os.Stdout</span><br><span class="line">rw := w.(io.ReadWriter) <span class="comment">// success: *os.File has both Read and Write</span></span><br><span class="line">w = <span class="built_in">new</span>(ByteCounter)</span><br><span class="line">rw = w.(io.ReadWriter) <span class="comment">// panic: *ByteCounter has no Read method</span></span><br></pre></td></tr></table></figure>
一般我们对一个接口值的动态类型是不确定的，而断言失败的代价是panic.又或者<br>我们更愿意去检验它是否是一些特定的类型。如果类型断言出现在一个预期有两个结果的赋值操作中，例如如下的定义，这个操作不会在失败的时候发生panic但是代替地返回一个额外的第二个结果，这个结果是一个标识成功的布尔值：</li>
</ul>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> w io.Writer = os.Stdout</span><br><span class="line">f, ok := w.(*os.File)      <span class="comment">// success:  ok, f == os.Stdout</span></span><br><span class="line">b, ok := w.(*bytes.Buffer) <span class="comment">// failure: !ok, b == nil</span></span><br></pre></td></tr></table></figure>

<p>第二个结果常规地赋值给一个命名为ok的变量。如果这个操作失败了，那么ok就是false值，第一个结果等于被断言类型的零值，在这个例子中就是一个nil的<code>*bytes.Buffer</code>类型。这个ok结果经常立即用于决定程序下面做什么。if语句的扩展格式让这个变的很简洁：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> f, ok := w.(*os.File); ok &#123;</span><br><span class="line">    <span class="comment">// ...use f...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="通过类型断言判断类型"><a href="#通过类型断言判断类型" class="headerlink" title="通过类型断言判断类型"></a>通过类型断言判断类型</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">"database/sql"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">listTracks</span><span class="params">(db sql.DB, artist <span class="keyword">string</span>, minYear, maxYear <span class="keyword">int</span>)</span></span> &#123;</span><br><span class="line">    result, err := db.Exec(</span><br><span class="line">        <span class="string">"SELECT * FROM tracks WHERE artist = ? AND ? &lt;= year AND year &lt;= ?"</span>,</span><br><span class="line">        artist, minYear, maxYear)</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>Exec方法使用SQL字面量替换在查询字符串中的每个’?’；SQL字面量表示相应参数的值，它有可能是一个布尔值，一个数字，一个字符串，或者nil空值。用这种方式构造查询可以帮助避免SQL注入攻击；</p>
<p>在Exec函数内部，我们可能会找到像下面这样的一个函数，它会将每一个参数值转换成它的SQL字面量符号</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">sqlQuote</span><span class="params">(x <span class="keyword">interface</span>&#123;&#125;)</span> <span class="title">string</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> x == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"NULL"</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> _, ok := x.(<span class="keyword">int</span>); ok &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Sprintf(<span class="string">"%d"</span>, x)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> _, ok := x.(<span class="keyword">uint</span>); ok &#123;</span><br><span class="line">        <span class="keyword">return</span> fmt.Sprintf(<span class="string">"%d"</span>, x)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> b, ok := x.(<span class="keyword">bool</span>); ok &#123;</span><br><span class="line">        <span class="keyword">if</span> b &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"TRUE"</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"FALSE"</span></span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> s, ok := x.(<span class="keyword">string</span>); ok &#123;</span><br><span class="line">        <span class="keyword">return</span> sqlQuoteString(s) <span class="comment">// (not shown)</span></span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(fmt.Sprintf(<span class="string">"unexpected type %T: %v"</span>, x, x))</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>switch语句可以简化if-else链，如果这个if-else链对一连串值做相等测试。一个相似的type switch（类型开关）可以简化类型断言的if-else链。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">switch</span> x.(<span class="keyword">type</span>) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="literal">nil</span>:       <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">case</span> <span class="keyword">int</span>, <span class="keyword">uint</span>: <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">case</span> <span class="keyword">bool</span>:      <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">case</span> <span class="keyword">string</span>:    <span class="comment">// ...</span></span><br><span class="line">        <span class="keyword">default</span>:        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>
<h3 id="接口-类型断言-判断是否包含函数"><a href="#接口-类型断言-判断是否包含函数" class="headerlink" title="接口+类型断言 判断是否包含函数"></a>接口+类型断言 判断是否包含函数</h3><p>在net/http包中web服务器有一个负责写入HTTP头字段（例如：”Content-type:text/html）的方法,io.Writer接口类型的变量w代表HTTP响应；写入它的字节最终被发送到某个人的web浏览器上。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">writeHeader</span><span class="params">(w io.Writer, contentType <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> _, err := w.Write([]<span class="keyword">byte</span>(<span class="string">"Content-Type: "</span>)); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> _, err := w.Write([]<span class="keyword">byte</span>(contentType)); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<p>因为Write方法需要传入一个byte切片而我们希望写入的值是一个字符串，</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// writeString writes s to w.</span></span><br><span class="line"><span class="comment">// If w has a WriteString method, it is invoked instead of w.Write.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">writeString</span><span class="params">(w io.Writer, s <span class="keyword">string</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">    <span class="keyword">type</span> stringWriter <span class="keyword">interface</span> &#123;</span><br><span class="line">        WriteString(<span class="keyword">string</span>) (n <span class="keyword">int</span>, err error)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> sw, ok := w.(stringWriter); ok &#123;</span><br><span class="line">        <span class="keyword">return</span> sw.WriteString(s) <span class="comment">// avoid a copy</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> w.Write([]<span class="keyword">byte</span>(s)) <span class="comment">// allocate temporary copy</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>因为Write方法需要传入一个byte切片而我们希望写入的值是一个字符串，所以我们需要使用[]byte(…)进行转换。</p>
<p>参数io.Writer接口告诉我们关于w持有的具体类型的唯一东西：就是可以向它写入字节切片。但事实上net/http包中的w变量持有的动态类型有一个允许字符串<strong>高效</strong>写入的WriteString方法,这个方法会避免去分配一个临时的拷贝。（许多满足io.Writer接口的重要类型同时也有WriteString方法，包括<code>*bytes.Buffer</code>，<code>*os.File</code>和<code>*bufio.Writer</code>。）</p>
<p>但是我们不能假设任意io.Writer类型的变量w都拥有WriteString方法。但是我们可以<strong>定义一个只有这个方法的新接口并且使用类型断言来检测是否w的动态类型满足这个新接口</strong>。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// writeString writes s to w.</span></span><br><span class="line"><span class="comment">// If w has a WriteString method, it is invoked instead of w.Write.</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">writeString</span><span class="params">(w io.Writer, s <span class="keyword">string</span>)</span> <span class="params">(n <span class="keyword">int</span>, err error)</span></span> &#123;</span><br><span class="line">    <span class="keyword">type</span> stringWriter <span class="keyword">interface</span> &#123;</span><br><span class="line">        WriteString(<span class="keyword">string</span>) (n <span class="keyword">int</span>, err error)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> sw, ok := w.(stringWriter); ok &#123;</span><br><span class="line">        <span class="keyword">return</span> sw.WriteString(s) <span class="comment">// avoid a copy</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> w.Write([]<span class="keyword">byte</span>(s)) <span class="comment">// allocate temporary copy</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">writeHeader</span><span class="params">(w io.Writer, contentType <span class="keyword">string</span>)</span> <span class="title">error</span></span> &#123;</span><br><span class="line">    <span class="keyword">if</span> _, err := writeString(w, <span class="string">"Content-Type: "</span>); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> _, err := writeString(w, contentType); err != <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> err</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>




<h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>在Go语言中只有<strong>当两个或更多的类型实现一个接口时</strong>才使用接口，它们必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法（经常和io.Writer或 fmt.Stringer一样只有一个）的更小的接口。当需要新建新的类型时，小的接口更容易满足需求来进行组合。对于接口设计的一个好的标准就是 ask only for what you need（只考虑你需要的东西）</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul>
<li>接口可以约定不同类型具有的共同操作,从而方便提取共同的操作提高代码质量</li>
<li>golang接口通过隐式实现,不需要额外声明</li>
<li>不同的实现方法的接收者相应传递不同类型的参数</li>
<li>golang接口由两部分组成分别是动态类型和动态值</li>
<li>golang接口动态值持有的实质上是原数据的一个拷贝</li>
<li>包含 nil 指针的接口不是 nil 接口</li>
<li>golang接口只有在只声明它但不做初始化，或者直接把字面量nil赋给它的时候才是真正的nil</li>
<li>类型断言是用于接口的类型转换</li>
<li>类型断言失败的错误是直接panic,最好使用安全类型断言</li>
</ul>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li><a href="https://books.studygolang.com/gopl-zh/ch7/ch7.html" target="_blank" rel="noopener">Go程序设计语言-接口</a></li>
<li><a href="https://my.oschina.net/chai2010/blog/161418" target="_blank" rel="noopener">Go语言的类型转换和类型断言</a></li>
<li><a href="https://www.jianshu.com/p/28dc038d6c6b" target="_blank" rel="noopener">interface{}==nil 几种坑及原理</a></li>
</ul>
<p>### </p>

        
<script src="/blog/js/qrious.min.js"></script>


        <hr>
        <div>
            
            <p>
                <span>专题: </span>
                
                <a href="/blog/series/#Golang学习">
                    <code key="Golang学习" class="post-label">Golang学习</code>
                </a>
                
            </p>
            
            <p>
                <span>本文发表于 2019-01-21，最后修改于 2020-03-17。</span>
            </p>
            <!-- 文章末尾的提示 start -->
            
            
            
                
            <!-- 文章末尾的提示 end -->
        </div>
        <hr />
        <p name="pagination" style="font-size: 1.2em">
    
    <a class="float-left" href="/blog/post/link.html">上一篇 « 友链</a>
    
    
    <a class="float-right" href="/blog/post/go-method-params.html">下一篇 » 从切片修改引发的go参数传递思考</a>
    
</p>
    </article>
    <!-- 赞赏 -->
    <!--打赏-->

    <!-- 推荐阅读三篇文章 -->
    <div class="col-xs-12">
        <h3>推荐阅读</h3>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/tcp/https.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#基础"
                    title="基础">基础</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#tcp/http"
                    title="tcp/http">tcp/http</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/tcp-what-is-https.html" class="title">
                简述HTTPS/SSL/TLS运行过程
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-02-22，</span>
                    <span class="hidden-xs">全文5324字，</span>
                    <span>阅读约18分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/tcp/tcp.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#基础"
                    title="基础">基础</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#tcp/http"
                    title="tcp/http">tcp/http</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/tcp-three-way-handshake.html" class="title">
                简述TCP“3次握手，4次挥手”
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2020-02-21，</span>
                    <span class="hidden-xs">全文9978字，</span>
                    <span>阅读约34分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
        <div class="post-preview">
    <div class="post-img" style="background-image: url('/blog/img/go.webp')"></div>
    <div class="post-info">
        <div class="post-info-center">
            <div class="hidden-xs">
                
                
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang"
                    title="Golang">Golang</a>
                <span>/</span>
                
                <a class="text-gray" href="/blog/tags/#Golang基础"
                    title="Golang基础">Golang基础</a>
                <span>/</span>
                
                
            </div>
            <a href="/blog/post/go-method-params.html" class="title">
                从切片修改引发的go参数传递思考
            </a>
            <p class="text-gray">
                <small>
                    <span>发表于2019-04-19，</span>
                    <span class="hidden-xs">全文5452字，</span>
                    <span>阅读约19分钟</span>
                </small>
            </p>
        </div>
    </div>
</div>
        
    </div>
    <div class="col-xs-12">
        <!-- 标签列表 -->
        <!-- Featured Tags -->
<style>
    #featured-tag .post-tag-item {
        font-size: 12px;
        line-height: 30px;
        display: inline-block;
        height: 30px;
        margin: 5px 0px;
        padding: 0 7px;
        color: #333;
        border-radius: 15px;
        background: #f6f6f6;
    }

    #featured-tag .post-tag-item:hover {
        color: #337ab7;
    }
</style>
<div id="featured-tag">
    
    <a class="post-tag-item" href="/blog/tags/#关于我" title="关于我"
        rel="1">#关于我 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#友链" title="友链"
        rel="1">#友链 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang汇编" title="Golang汇编"
        rel="4">#Golang汇编 (4) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang底层" title="Golang底层"
        rel="5">#Golang底层 (5) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang" title="Golang"
        rel="8">#Golang (8) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang基础" title="Golang基础"
        rel="1">#Golang基础 (1) </a>
    
    <a class="post-tag-item" href="/blog/tags/#Golang源码" title="Golang源码"
        rel="2">#Golang源码 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#基础" title="基础"
        rel="2">#基础 (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#tcp/http" title="tcp/http"
        rel="2">#tcp/http (2) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL" title="MySQL"
        rel="3">#MySQL (3) </a>
    
    <a class="post-tag-item" href="/blog/tags/#MySQL优化" title="MySQL优化"
        rel="1">#MySQL优化 (1) </a>
    
</div>
    </div>
    <!-- 评论 -->
    <div class="col-xs-12">
        
    </div>
</div>
<aside class="float-left gkt-sidebar hidden-xs hidden-sm">
    <div style="clear: both"></div>
    <div class="gkt-sidebar-wrapper">
        <section class="box-shadow"><style>
    .gkt-summary {
        border: 1px solid #DDDDDD;
        border-radius: 3px;
        padding: 5px;
        width: 100%;
    }


    .gkt-summary nav {
        overflow: hidden;
    }

    .gkt-summary nav a {
        display: inline-block;
        text-align: center;
        color: #333;
        font-size: 12px;
    }

    .gkt-summary nav span {
        display: block;
    }

    .gkt-summary nav .middle {
        border-left: 1px solid #eaecef;
        border-right: 1px solid #eaecef;
    }

    .gkt-summary .number {
        font-weight: bold;
    }

    .gkt-summary .link-list {
        margin-top: 5px;
        margin-bottom: -5px;
        padding-top: 7px;
        border-top: 1px dashed #999;
        display: flex;
    }

    .gkt-summary .link-list a {
        flex: 1;
    }

    .gkt-summary .link-list img {
        width: 25px;
        height: 25px;
    }
</style>

<div class="gkt-summary">
    <nav>
        <a href="/blog/" class="col-xs-4">
            <number class="number">15</number><span>文章</span>
        </a>
        <a href="/blog/series" class="col-xs-4 middle">
            <number class="number">5</number><span>专题</span>
        </a>
        <a href="/blog/tags" class="col-xs-4">
            <number class="number">11</number><span>标签</span>
        </a>
    </nav>

    
    <div class="link-list">
        
        <a href="https://marvelthanos.com" target="_blank" rel="noopener"><img src="/blog/img/related_links/thanos.webp" /></a>
        
    </div>
    
</div></section>
        
        
        <section class="gkt-sidebar-content box-shadow">
            <strong>Golang学习</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-interface.html"
                        class="gkt-sidebar-active">Go学习 接口和类型断言</a>
                    
                    <!-- Table of Contents -->
<div id="sidebar-toc">
  <!-- TOC  -->
  
  <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#简介"><span class="toc-nav-text">简介</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#接口值"><span class="toc-nav-text">接口值</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#一个包含nil指针的接口不是nil接口"><span class="toc-nav-text">一个包含nil指针的接口不是nil接口</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#类型断言"><span class="toc-nav-text">类型断言</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#小结"><span class="toc-nav-text">小结</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#总结"><span class="toc-nav-text">总结</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#参考资料"><span class="toc-nav-text">参考资料</span></a></li></ol>
  
</div>

<script>
  (function () {
    var h2 = document.querySelectorAll('article h2');
    var h3 = document.querySelectorAll('article h3');
    var linkList = document.querySelectorAll('#sidebar-toc a');

    function findLinkElement(name) {
      for (var i = 0; i < linkList.length; i++) {
        var items = linkList[i].href.split('#');
        if (items && items[items.length - 1] === encodeURIComponent(name)) {
          return i;
        }
      }
      return -1;
    }

    function activeLink(titleList) {
      var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      for (var i = titleList.length - 1; i >= 0; i--) {
        if (scrollTop - titleList[i].offsetTop > 0) {
          var index = findLinkElement(titleList[i].id);
          index != -1 && linkList[index].classList.add('gkt-sidebar-active');
          break;
        }
      }
    }

    window.addEventListener("scroll", function (e) {
      [].slice.call(linkList).forEach(function (link) {
        link.classList.remove('gkt-sidebar-active');
      })
      activeLink(h2);
    })
  })();
</script>
                    
                </li>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html"
                        class="">Go源码学习 sort包</a>
                    
                </li>
                
            </ul>
        </section>
        
        
        <section class="box-shadow">
            <strong>最近的文章</strong>
            <ul>
                
                <li>
                    <a href="/blog/post/go-learn-lib-sorts.html">Go源码学习 sort包</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-optimize-explain.html">EXPLAIN-执行计划查询命令</a>
                </li>
                
                <li>
                    <a href="/blog/post/mysql-index-base.html">《高性能MySQL》笔记-索引基础</a>
                </li>
                
            </ul>
        </section>
        <section class="box-shadow text-center">
            
        </section>
    </div>
</aside>

<script>
    (function () {
        var ele = getDom('.gkt-sidebar-content')
        var wrapper = getDom('.gkt-sidebar-wrapper')
        var last = 0
        window.addEventListener("scroll", function (e) {
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            var isDown = scrollTop > last;
            var pos = getPosition(ele).y - scrollTop;
            var downLimit = 50;
            var upLimit = -100;
            // uarrow.style.marginTop = scrollTop + 'px';
            isDown && pos <= downLimit && wrapper.classList.add("gkt-sidebar-fixed");
            !isDown && pos > upLimit && wrapper.classList.remove("gkt-sidebar-fixed");
            last = scrollTop
        })
    })();
    (function () {
        function resizeUArrow() {
            var s = getDom('.u-arrow-wrapper').style
            var pc = getDom('.post-container')
            s.left = getPosition(pc).x + 'px';
            s.width = pc.clientWidth + 'px';
        }
        resizeUArrow()
        window.addEventListener('resize', resizeUArrow);
    })();
</script>

    
</main>
    </div>
    <style>
    img#go-top {
        position: fixed;
        bottom: 100px;
        width: 50px;
        cursor: pointer;
        z-index: 9999;
    }
</style>
<img id="go-top" src="/blog/icon/top.png" class="hidden-xs" style="display: none" />
<script>
    (function () {
        var goTop = document.getElementById('go-top');
        var mainContainer = document.querySelector('.main-container');
        
        goTop.addEventListener('click', function () {
            window.scroll(0, 0);
        }, false);
        window.addEventListener('scroll', function () {
            var right = document.body.offsetWidth - mainContainer.getBoundingClientRect().right;
            var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            goTop.style.right = right + 10 + 'px'
            scrollTop > 700 && (goTop.style.display = "block");
            scrollTop <= 700 && (goTop.style.display = "none");
        });
    })();
</script>
    <style>
    #geektutu-click-img-container {
        position: fixed;
        left: 0;
        top: 0;
        text-align: center;
        width: 100%;
        display: none;
        z-index: 9999;
    }

    #geektutu-click-img-container img {
        object-fit: contain;
        background: #eaecef;
        padding: 15px;
        border-radius: 10px;
        height: auto;
        width: auto;
        vertical-align: middle;
    }
</style>


<div id="geektutu-click-img-container">
    <img src="" alt="Big Image">
</div>

<script>
    (function () {
        var container = document.querySelector('#geektutu-click-img-container')
        var targetImg = container.querySelector('img')
        var imgs = document.querySelectorAll('article img');
        targetImg.addEventListener('click', function (e) {
            container.style.display = 'none';
            e && e.stopPropagation && e.stopPropagation();
        }, false);

        for (var i = 0; i < imgs.length; ++i) {
            var img = imgs[i];
            img.addEventListener('click', (function (src, rate) {
                return function (e) {
                    e && e.stopPropagation && e.stopPropagation();
                    if (window.innerWidth < 980) {
                        return
                    }
                    targetImg.style.height = targetImg.style.width = 'auto';
                    if (window.innerWidth / window.innerHeight > rate) {
                        targetImg.style.height = (window.innerHeight - 20) + 'px';
                    } else {
                        targetImg.style.width = (window.innerWidth - 20) + 'px';
                    }
                    container.style.height = window.innerHeight + 'px'
                    container.style.lineHeight = window.innerHeight + 'px'
                    container.style.display = 'block';
                    targetImg.src = src;
                };
            }(img.src, img.width / img.height)), false)
        }
    })();
</script>
    <!-- Footer -->
    <!-- Footer -->
<style>
    footer {
        width: 100%;
        line-height: 1.5;
        padding: 20px;
    }

    footer a {
        color: #333;
        text-decoration: none;
    }

    .footer-hexo img {
        height: 20px;
        margin-bottom: -5px;
    }

    .footer-hexo a {
        color: #337ab7;
    }
</style>
<footer class="text-center col-xs-12">
    <p>
        <small>© 2020 - Evrn - </small>
        <small>
            <a target="_blank" rel="nofollow noopener" href="http://www.miitbeian.gov.cn/"></a>
        </small>
    </p>
    <p class="footer-hexo">
        <!-- 但若直接使用或修改主题，请务必保留这段声明 -->
        <small>Powered by <a target="_blank" href="https://hexo.io">Hexo</a> | Theme
            <a target="_blank" href="https://geektutu.com">Geektutu</a>
            <a href="https://github.com/geektutu/hexo-theme-geektutu" target="_blank" rel="noopener">
                <img src=""
                    alt="Github Star">
            </a>
        </small>
    </p>
    
    <p>
        <small>
            <span id="busuanzi_container_site_pv">👁<span id="busuanzi_value_site_pv"></span></span> &nbsp;
            <span id="busuanzi_container_page_pv">📚<span id="busuanzi_value_page_pv"></span></span>
        </small>
    </p>
    
    

</footer>


<script>
    window.addEventListener('load', function () {
        globalAddScript('//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js');
    })
</script>

<!-- Baidu Tongji -->





    <script>
        // 非本站网址，新开tab页签
        (function () {
            var stopBubble = function (e) {
                e && e.stopPropagation && e.stopPropagation();
            }
            var links = document.querySelectorAll('a');
            [].slice.call(links).forEach(function (item) {
                if (item.href && item.href.indexOf(window.location.host) === -1) {
                    item.target = '_blank'
                }
                // 阻止冒泡，不触发彩蛋。
                item.addEventListener('click', stopBubble, false);
            });
            var article = document.querySelector('article');
            article && article.addEventListener('click', stopBubble, false)
        })();
    </script>
    
</body>

</html>